/*
 * Copyright 2008 Konstantin Triger <kostat@gmail.com> 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0 
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jaque;

import jaque.functions.Predicate;
import static jaque.Query.*;

import java.util.*;

/**
 * Provides a standard set of business rules to be applied with {@link Query}
 * class. The rules are immutable and thus can be instantiated once and used
 * everywhere. The rules are designed to be combined to create more
 * sophisticated ones, i.e. {@code or(greaterThan(...), lessThan(...))}.
 * 
 * @author <a href="mailto://object_streaming@googlegroups.com">Konstantin Triger</a>
 */
public final class Operation {

	private Operation() {
	}

	/**
	 * Determines whether all objects in a stream satisfy a boolean condition.
	 * 
	 * @param <T>
	 *            the type of the objects in the stream.
	 * @param source
	 *            the input object stream.
	 * @param predicate
	 *            a function representing a boolean condition.
	 * @return true if predicate returned true for all the objects in the stream
	 *         or if the stream is empty; otherwise false.
	 * @throws Throwable
	 */
	public static <T> boolean all(final Iterable<T> source,
			final Predicate<? super T> predicate) throws Throwable {
		for (T t : source)
			if (!predicate.invoke(t))
				return false;

		return true;
	}

	/**
	 * Determines whether the object stream is empty.
	 * 
	 * @param <T>
	 *            the type of the objects in the stream.
	 * @param source
	 *            the input object stream.
	 * @return true if the stream does not contain objects; otherwise false.
	 */
	public static <T> boolean isEmpty(final Iterable<T> source) {
		return !source.iterator().hasNext();
	}

	/**
	 * Determines whether any object in a stream satisfies a boolean condition.
	 * 
	 * @param <T>
	 *            the type of the objects in the stream.
	 * @param source
	 *            the input object stream.
	 * @param predicate
	 *            a function representing a boolean condition.
	 * @return true if predicate returned true for any object in the stream;
	 *         otherwise false.
	 * @throws Throwable
	 */
	public static <T> boolean any(final Iterable<T> source,
			final Predicate<? super T> predicate) throws Throwable {
		return !isEmpty(where(source, predicate));
	}

	/**
	 * Counts objects in a stream.
	 * 
	 * @param <T>
	 *            the type of the objects in the stream.
	 * @param source
	 *            the input object stream.
	 * @return Integer representing the number of objects in the stream.
	 * @throws Throwable
	 */
	public static <T> int count(final Iterable<T> source) throws Throwable {
		int c = 0;

		for (Iterator<T> iter = source.iterator(); iter.hasNext(); iter.next(), c++)
			;

		return c;
	}

	/**
	 * Counts objects satisfying a boolean condition in a stream.
	 * 
	 * @param <T>
	 *            the type of the objects in the stream.
	 * @param source
	 *            the input object stream.
	 * @param predicate
	 *            a function representing a boolean condition.
	 * @return Integer representing the number of objects satisfying a boolean
	 *         condition in the stream.
	 * @throws Throwable
	 */
	public static <T> int count(final Iterable<T> source,
			final Predicate<? super T> predicate) throws Throwable {
		return count(where(source, predicate));
	}

	/**
	 * Counts objects in a stream.
	 * 
	 * @param <T>
	 *            the type of the objects in the stream.
	 * @param source
	 *            the input object stream.
	 * @return Long representing the number of objects in the stream.
	 * @throws Throwable
	 */
	public static <T> long longCount(final Iterable<T> source) throws Throwable {
		long c = 0;

		for (Iterator<T> iter = source.iterator(); iter.hasNext(); iter.next(), c++)
			;

		return c;
	}

	/**
	 * Counts objects satisfying a boolean condition in a stream.
	 * 
	 * @param <T>
	 *            the type of the objects in the stream.
	 * @param source
	 *            the input object stream.
	 * @param predicate
	 *            a function representing a boolean condition.
	 * @return Long representing the number of objects satisfying a boolean
	 *         condition in the stream.
	 * @throws Throwable
	 */
	public static <T> long longCount(final Iterable<T> source,
			final Predicate<? super T> predicate) throws Throwable {
		return longCount(where(source, predicate));
	}

	/**
	 * Returns an object at specified position in a stream.
	 * 
	 * @param <T>
	 *            the type of the objects in the stream.
	 * @param source
	 *            the input object stream.
	 * @param position
	 *            position of object in the stream.
	 * @return an object at specified position in a stream.
	 */
	public static <T> T elementAt(final Iterable<T> source, int position) {
		if (source instanceof List)
			return ((List<T>) source).get(position);

		if (position < 0)
			throw new IndexOutOfBoundsException();

		Iterator<T> iter = source.iterator();
		T t;
		do {
			if (!iter.hasNext())
				throw new IndexOutOfBoundsException();
			t = iter.next();
		} while (position-- > 0);

		return t;
	}

	/**
	 * Returns an object at specified position in a stream satisfying a boolean
	 * condition.
	 * 
	 * @param <T>
	 *            the type of the objects in the stream.
	 * @param source
	 *            the input object stream.
	 * @param position
	 *            position of object in the stream.
	 * @param predicate
	 *            a function representing a boolean condition.
	 * @return an object at specified position in a stream satisfying a boolean
	 *         condition.
	 */
	public static <T> T elementAt(final Iterable<T> source, int position,
			final Predicate<? super T> predicate) {
		return elementAt(where(source, predicate), position);
	}

	/**
	 * Returns the first object in the stream.
	 * 
	 * @param <T>
	 *            the type of the objects in the stream.
	 * @param source
	 *            the input object stream.
	 * @return a first object.
	 */
	public static <T> T first(final Iterable<T> source) {
		return elementAt(source, 0);
	}

	/**
	 * Returns the first object in the stream satisfying a boolean condition.
	 * 
	 * @param <T>
	 *            the type of the objects in the stream.
	 * @param source
	 *            the input object stream.
	 * @param predicate
	 *            a function representing a boolean condition.
	 * @return a first object satisfying a boolean condition.
	 */
	public static <T> T first(final Iterable<T> source,
			final Predicate<? super T> predicate) {
		return first(where(source, predicate));
	}

	/**
	 * Returns the last object in the stream.
	 * 
	 * @param <T>
	 *            the type of the objects in the stream.
	 * @param source
	 *            the input object stream.
	 * @return a last object.
	 */
	public static <T> T last(final Iterable<T> source) {
		if (source instanceof List) {
			List<T> gsource = (List<T>) source;
			return gsource.get(gsource.size() - 1);
		}
		Iterator<T> iter = source.iterator();
		if (!iter.hasNext())
			throw new IndexOutOfBoundsException();

		T r;
		do {
			r = iter.next();
		} while (iter.hasNext());

		return r;
	}

	/**
	 * Returns the last object in the stream satisfying a boolean condition.
	 * 
	 * @param <T>
	 *            the type of the objects in the stream.
	 * @param source
	 *            the input object stream.
	 * @param predicate
	 *            a function representing a boolean condition.
	 * @return a last object satisfying a boolean condition.
	 */
	public static <T> T last(final Iterable<T> source,
			final Predicate<? super T> predicate) {
		return last(where(source, predicate));
	}
}
